package peersim.EP2300.tasks;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import peersim.EP2300.base.GAPProtocolBase;
import peersim.EP2300.message.ResponseTimeArriveMessage;
import peersim.EP2300.transport.ConfigurableDelayTransport;
import peersim.cdsim.CDProtocol;
import peersim.config.Configuration;
import peersim.config.FastConfig;
import peersim.core.CommonState;
import peersim.core.Linkable;
import peersim.core.Node;
import peersim.edsim.EDProtocol;

public class GAPExtension1 extends GAPProtocolBase implements EDProtocol, CDProtocol{

	public static final long SELF = 1;
	public static final long PEER = 2;
	public static final long PARENT = 0;
	public static final long CHILD = 4;
	
	private static final String NODE_ID = "nodeId";
	private static final String NODE_STATUS = "status";
	private static final String NODE_PARENT = "parent";
	private static final String NODE_LEVEL = "level";
	private static final String NODE_AGGRE = "aggregate";

	private long root = 0;
	private long level = Integer.MAX_VALUE - 1;
	private long parent = Integer.MAX_VALUE - 1;
	Map<Long, Long> aggre = new HashMap<Long, Long>();
	private long status; 
	
	long timestamp = 0;
	long local_var = 0;
	int children = 0;
	int total = 1;
	double error = Configuration.getDouble("e");
	double e = 0;
	long delta_t = Configuration.getLong("delta_t");
	long endTime = Configuration.getLong("delta_t");
	boolean flag = false;
	
	ResponseTimeArriveMessage msg;	
	UpdateVector updateVector = new UpdateVector(0, 0, 0);
	Message counting;
	List<Node> neighbors = new ArrayList<Node>();	
	Map<Long, Map<String, Long>> n_table = new HashMap<Long, Map<String, Long>>();	
	Map<Node, Integer> leaves = new HashMap<Node, Integer>();
	
	public GAPExtension1(String prefix) {
		super(prefix);
	}

	@Override
	public void processEvent(Node node, int pid, Object event) {
		// Implement your event-driven code for task 2 here
		Map<String, Long> tmp;		
		
		Linkable link = (Linkable)node.getProtocol(FastConfig.getLinkable(pid));

		if(n_table.size()==0){	
			ConfigurableDelayTransport timewindow = new ConfigurableDelayTransport();
			timewindow.setDelay(delta_t - CommonState.getTime());
			timewindow.send(node, node, new Delay(Delay.DELAY, 1000), pid);
			
			if(node.getID() == root){
				level = 0;
				parent = 0;
				status = PARENT;		
				
				tmp  = new HashMap<String, Long>();
				
				tmp.put(NODE_ID, root);
				tmp.put(NODE_STATUS, status);
				tmp.put(NODE_LEVEL, (long) -1);
				tmp.put(NODE_AGGRE, (long) 0);
				
				n_table.put(node.getID(), tmp);

				status = SELF;
				
				tmp  = new HashMap<String, Long>();
				
				tmp.put(NODE_ID, root);
				tmp.put(NODE_STATUS, status);
				tmp.put(NODE_LEVEL, level);
				tmp.put(NODE_AGGRE, (long) 0);
				
				n_table.put(node.getID(), tmp);				
				
			}
			
			else{
				level = Integer.MAX_VALUE - 1;
				parent = Integer.MAX_VALUE - 1;
				status = SELF;
				
				tmp  = new HashMap<String, Long>();
				
				tmp.put(NODE_ID, node.getID());
				tmp.put(NODE_STATUS, status);
				tmp.put(NODE_LEVEL, level);
				tmp.put(NODE_AGGRE, (long) 0);
				
				n_table.put(node.getID(), tmp);
				
			}
			
			updateVector = new UpdateVector(level, parent, local_var); 
			counting = new Message(Message.TREE, node, 0);
			
			if(link.degree() > 0){				
				
				for(int i = 0; i < link.degree(); i++){
					
					Node peer = link.getNeighbor(i);
					 
					tmp  = new HashMap<String, Long>();
					neighbors.add(peer);

					sendWithInstTransport(node, peer, new Message(Message.UPDATE, node, updateVector));
				 
				}
				
			}					

		}
		
		else{
			if(link.degree() > 0){
				for(int i = 0; i < link.degree(); i++){
					 Node peer = link.getNeighbor(i);
					 
					 if(!n_table.containsKey(peer.getID())){
						 sendWithInstTransport(peer, node, new Message(Message.NEW, peer));
					 }
				 
				}
				
			}			
		}

		if(event.getClass().equals(new ResponseTimeArriveMessage(0).getClass())){
			
			msg = (ResponseTimeArriveMessage)event;
			long startTime = Math.max(0, endTime - delta_t);	

			if(CommonState.getTime() >= startTime && CommonState.getTime() < endTime){
				aggre.put(CommonState.getTime(), msg.getResponseTime());
				
				if(msg.getResponseTime() > local_var){
					local_var = msg.getResponseTime();		
				}
			}
	
		}
		
		else if(event.getClass().equals(new Delay("", 0).getClass())){
			Delay msg = (Delay)event;	
			
			if(msg.delay.equals(Delay.DELAY)){
				endTime += msg.delay_time;
				long startTime = Math.max(0, endTime - delta_t);
				long max_tmp = 0;
				
				Iterator<Entry<Long, Long>> iter = aggre.entrySet().iterator();
				while(iter.hasNext()){
					Entry<Long,Long> entry = iter.next();
					
					if(entry.getKey() >= startTime && entry.getKey() < endTime){
						max_tmp = Math.max(max_tmp, entry.getValue());
						if(max_tmp == entry.getValue()){
							timestamp = entry.getKey();
						}
					}

				}

				local_var = max_tmp;
				
				ConfigurableDelayTransport timewindow = new ConfigurableDelayTransport();
				timewindow.setDelay(msg.delay_time);
				timewindow.send(node, node, new Delay(Delay.DELAY, 1000), pid);
			}

		}
		
		else if(event.getClass().equals(new Message(0, null).getClass()) || event.getClass().equals(new Message(0, null, 0).getClass()) || event.getClass().equals(new Message(0, null, new UpdateVector(0, 0, 0)).getClass())){
			Message msg = (Message)event;
			
			switch(msg.tag){
			case Message.TREE:
				if(msg.src.getID() == parent){
					if(total!=msg.children){
						total = msg.children;
						if(e!=(error/msg.children)){
							e = error/msg.children;
							
							Iterator<Node> iter = (Iterator<Node>)leaves.keySet().iterator();
							while(iter.hasNext()){
								sendWithInstTransport(node, iter.next(), new Message(Message.TREE, node, total));
							}
							//System.out.println(node.getID() + " - " + total);
						}
					}
				}
				else leaves.put(msg.src, msg.children);
				break;
			case Message.NEW:
				neighbors.add(msg.src);				
				break;
			case Message.FAIL:
				n_table.remove(msg.src.getID());
				break;
			case Message.LOCALVAR:
				break;
			case Message.UPDATE:
				if(!n_table.containsKey(msg.src.getID())){
					tmp  = new HashMap<String, Long>();

					tmp.put(NODE_ID, msg.src.getID());
					tmp.put(NODE_PARENT, msg.update.parent);
					tmp.put(NODE_LEVEL, msg.update.level);
					tmp.put(NODE_AGGRE, msg.update.aggregate);
					
					n_table.put(msg.src.getID(), tmp);
				}

				else{

					n_table.get(msg.src.getID()).put(NODE_PARENT, msg.update.parent);
					n_table.get(msg.src.getID()).put(NODE_LEVEL, msg.update.level);
					n_table.get(msg.src.getID()).put(NODE_AGGRE, msg.update.aggregate);
				}
				break;
			}						

		}			

		long min_level = Integer.MAX_VALUE - 1;
		long min_index = Integer.MAX_VALUE - 1;		

		if(node.getID()!= 0){
			
			if(n_table.containsKey((long)root)){
				level = 1;
				parent = 0;
				n_table.get(node.getID()).put(NODE_LEVEL, level);
				n_table.get(node.getID()).put(NODE_PARENT, parent);
				n_table.get((long)root).put(NODE_STATUS, PARENT);
			}

			else{
				Iterator<Entry<Long, Map<String, Long>>> iter = n_table.entrySet().iterator();
				
				while(iter.hasNext()){
					Map.Entry<Long, Map<String, Long>> pairs = (Map.Entry<Long, Map<String, Long>>)iter.next();
					long temp = pairs.getValue().get(NODE_LEVEL);
					min_level = Math.min(min_level, temp);
					
				}
				
				if(min_level < level){
					level = min_level + 1;
					n_table.get(node.getID()).put(NODE_LEVEL, level);
					
					iter = n_table.entrySet().iterator();
					
					while(iter.hasNext()){
						Map.Entry<Long, Map<String, Long>> pairs = (Map.Entry<Long, Map<String, Long>>)iter.next();
						
						if(pairs.getValue().get(NODE_LEVEL) == min_level){
							long temp = pairs.getValue().get(NODE_ID);
							min_index = Math.min(min_index, temp);
						}
						
					}
					
					parent = min_index;
					n_table.get(node.getID()).put(NODE_PARENT, parent);
				}

			}
			
		}
		else{
			level = 0;
			parent = 0;
		}

		Node p = null;
		for(int i = 0; i < link.degree(); i++){
			
			long id; 
			
			if(n_table.containsKey(link.getNeighbor(i).getID())){
				id = link.getNeighbor(i).getID();
				
				n_table.get(id).put(NODE_STATUS, PEER);
				
				if((int)id == parent){
					n_table.get(id).put(NODE_STATUS, PARENT);
					p = link.getNeighbor(i);
				}
				else{
					if(n_table.get(id).containsKey(NODE_PARENT)){
						Node child = link.getNeighbor(i);
						
						if(n_table.get(id).get(NODE_PARENT) == (int)node.getID()){
	 						n_table.get(id).put(NODE_STATUS, CHILD);	 						
	 						if(!leaves.containsKey(child)){
	 							leaves.put(child, 0);
	 						}
	 					}
						else{
							if(leaves.containsKey(child)){
								leaves.remove(child);
							}
						}
					}
				}
			}
			
		}
		
		long max_aggre = local_var;
		
		for(int i = 0; i < link.degree(); i++){						
			long id; 
			
			if(n_table.containsKey(link.getNeighbor(i).getID())){
				id = link.getNeighbor(i).getID();
				
				if(n_table.get(id).get(NODE_STATUS) == CHILD){
					max_aggre = Math.max(max_aggre, n_table.get(id).get(NODE_AGGRE));
				}
			}
			
		}
		
		if(Math.abs(max_aggre - n_table.get(node.getID()).get(NODE_AGGRE)) >e){
			n_table.get(node.getID()).put(NODE_AGGRE, max_aggre);
		}
		
		
		if(node.getID() == root){
			GAPProtocolBase rootProtocol = (GAPProtocolBase) node
					.getProtocol(Configuration.getPid("ACTIVE_PROTOCOL"));
			rootProtocol.setEstimateMax(max_aggre);
		}
				
		UpdateVector update = new UpdateVector(level, parent, n_table.get(node.getID()).get(NODE_AGGRE));
		
		if((update.level!=updateVector.level) || (update.parent!=updateVector.parent) || update.aggregate!=updateVector.aggregate){			

			for(int i = 0; i < link.degree(); i++){
				sendWithInstTransport(node, link.getNeighbor(i), new Message(Message.UPDATE, node, update));
			}
			updateVector = update;															

			
		}	
		else{
			if((p!=null && (leaves.size() > 0))||((node.getID() == root) && (link.degree() == leaves.size())) ){
				int count = 0;
				Iterator<Entry<Node, Integer>> iterator = (Iterator<Entry<Node, Integer>>)leaves.entrySet().iterator();
				while(iterator.hasNext()){
					Entry<Node, Integer> entry = (Entry<Node, Integer>)iterator.next();
					count += entry.getValue();
				}
				count += leaves.size();
				
				if(count != counting.children){
					
					if(node.getID()!= root){
						sendWithInstTransport(node, p, counting);						
					}
					counting = new Message(Message.TREE, node, count);
				}
				else{
					if(node.getID()== root){
						if(total != (count+1)){
							//System.out.println(node.getID());
							total = count + 1;
							for(int i = 0; i < neighbors.size(); i++){
								sendWithInstTransport(node, neighbors.get(i), new Message(Message.TREE, node, total));
								
							}
						}
						
					}
				}
				
			}
		}

	}
	
	@Override
	public void nextCycle(Node node, int protocolID) {
		// Implement your cycle-driven code for task 2 here
		
	}
	
	
	public GAPExtension1 clone() {
		return new GAPExtension1(prefix);

	}
	
	
	

}
